package org.hzero.business;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 流水号工具类
 * <p>
 * 支持按 年月日+001递增 格式生成
 *
 * @author tianhao.luo@hand-china.com
 */
public class KnifeSerialNumberUtil {
    public static final Logger logger = LoggerFactory.getLogger(KnifeSerialNumberUtil.class);

    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * redis库
     */
    private static final Integer REDIS_DB = 10;

    /**
     * 存储pms业务数据的key的头
     */
    private static final String REDIS_EAS_KEY_HEADER = "PMS:";

    /**
     * 单据编号前缀
     */
    private static final String REDIS_ORDER_KEY_PREFIX = "ORDER_NUMBER:";

    /**
     * @return 当前年月日的字符串
     */
    private static String getCurrYearMonthDay() {
        ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
        readLock.lock();
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            return sdf.format(new Date());
        } finally {
            readLock.unlock();
        }
    }

    /**
     * 按年月日格式生成流水号
     * <p>
     * 优点是支持单据编号自动递增，且不会重复。缺点是不支持回滚
     *
     * @param orderType 单据类型
     * @return 流水号
     */
    @Deprecated
    public static String formatByYearMonthDay(OrderType orderType) {
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
        writeLock.lock();
        try (Jedis jedis = KnifeRedisUtils.getJedis()) {
            // 一定要注意使用的数据库
            jedis.select(REDIS_DB);
            String currYearMonthDay = getCurrYearMonthDay();
            String redisKey = REDIS_EAS_KEY_HEADER + REDIS_ORDER_KEY_PREFIX + orderType.header + currYearMonthDay;
            Long aLong = jedis.incrBy(redisKey, 1);
            if (aLong == 1) {
                jedis.expire(redisKey, 60 * 60 * 24);
            }
            // 自动补0
            String format = String.format("%03d", aLong);
            return orderType.header + currYearMonthDay + format;
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * 获取redis编号
     * <p>
     * 优点是支持单据编号自动递增，且不会重复。同时支持自己修改的数据回滚，但不支持其他线程同时修改再回滚。
     *
     * @param orderType 单据类型
     * @return 单据编号对象
     */
    public static RedisOrderNumber formatByYearMonthDayWithVersion(OrderType orderType) {
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
        writeLock.lock();
        try (Jedis jedis = KnifeRedisUtils.getJedis()) {
            // 一定要注意使用的数据库
            jedis.select(REDIS_DB);
            String currYearMonthDay = getCurrYearMonthDay();
            String redisKey = REDIS_EAS_KEY_HEADER + REDIS_ORDER_KEY_PREFIX + orderType.header + currYearMonthDay;

            Long orderSerialNumber = jedis.hincrBy(redisKey, RedisOrderNumber.ORDER_NUMBER, 1);
            // 同时新增版本号
            Long objVersion = jedis.hincrBy(redisKey, RedisOrderNumber.OBJECT_VERSION_NUMBER, 1);
            if (orderSerialNumber == 1) {
                jedis.expire(redisKey, 60 * 60 * 24);
            }
            // 自动补0
            String format = String.format("%03d", orderSerialNumber);
            String orderNumber = orderType.header + currYearMonthDay + format;
            return new RedisOrderNumber(objVersion, orderNumber, orderType);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * 回退单据编号
     *
     * @param redisOrderNumber 插入时生成的单据编号对象
     */
    public static void revokeOrderNumber(RedisOrderNumber redisOrderNumber) {
        boolean b = compareAndSetOrderNumber(redisOrderNumber);
        logger.debug("{}回退结果：{}", redisOrderNumber.orderNumber, b);
    }


    /**
     * ABA问题：假如线程1通过CAS修改变量a的值，先去获取到变量a的值X；此时线程2通过CAS修改变量a的值，读到的值为X，修改为B，然后又使用CAS修改为X；
     * 此时线程1执行修改变量a的值是X，但是这个X已经不是开始获取的那个值了
     * <p>
     * 回退单据编号，使用版本号避免ABA问题
     * <p>
     * 能回退编号的情况是：线程1获取单据编号成功，但是业务出现异常需要回滚，此时没有其他线程进行修改redis中的编号，就可以进行回滚；
     * 否则有并发线程操作了单据编号，不做回退处理
     *
     * @param redisOrderNumber 通过{@code formatByYearMonthDayHash}方法返回的对象，回退时传入
     * @return 回退操作执行成功，返回true；否则返回false
     */
    private static boolean compareAndSetOrderNumber(RedisOrderNumber redisOrderNumber) {
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
        writeLock.lock();
        try (Jedis jedis = KnifeRedisUtils.getJedis()) {
            // 一定要注意使用的数据库
            jedis.select(REDIS_DB);
            String currYearMonthDay = getCurrYearMonthDay();
            String redisKey = REDIS_EAS_KEY_HEADER + REDIS_ORDER_KEY_PREFIX + redisOrderNumber.orderType.header + currYearMonthDay;
            // 获取redis中版本号
            String redisObjVersion = jedis.hget(redisKey, RedisOrderNumber.OBJECT_VERSION_NUMBER);
            String redisSerialNumber = jedis.hget(redisKey, RedisOrderNumber.ORDER_NUMBER);
            // 版本号匹配进行回退操作
            if (Long.parseLong(redisObjVersion) == redisOrderNumber.objectVersionNumber) {
                // 同时新增版本号
                jedis.hincrBy(redisKey, RedisOrderNumber.OBJECT_VERSION_NUMBER, 1);
                long revokeSerialNumber = Long.parseLong(redisSerialNumber) - 1;
                jedis.hset(redisKey, RedisOrderNumber.ORDER_NUMBER, String.valueOf(revokeSerialNumber));
                return true;
            }
            return false;
        } finally {
            writeLock.unlock();
        }
    }


    public enum OrderType {
        /**
         * 阶段变更
         */
        JD("JD"),
        /**
         * 课题类阶段变更
         */
        KTJD("KTJD"),
        /**
         * 阶段变更
         */
        JDBZ("JDBZ"),
        /**
         * 立项申请
         */
        LX("LX"),
        /**
         * 父项目基础信息变更
         */
        FJC("FJC"),
        /**
         * 父项目关键信息变更
         */
        FGJ("FGJ"),
        /**
         * 子项目信息变更
         */
        ZXM("ZXM"),
        /**
         * 阶段关闭
         */
        JSD("CDJSD"),
        /**
         * 项目退票申请
         */
        CDTP("CDTP"),
        /**
         * 项目开票申请
         */
        CDKP("CDKP"),
        /**
         * 子项目拆分
         */
        ZCF("ZCF"),
        /**
         * 后评估
         */
        HPG("HPG"),
        /**
         * 项目关联合同
         */
        GL("GL"),
        /**
         * 任务单
         */
        CDRWD("CDRWD");

        /**
         * 流水号开头格式
         */
        public String header;

        OrderType(String header) {
            this.header = header;
        }
    }

    public static class RedisOrderNumber {

        /**
         * 版本号
         */
        protected static final String OBJECT_VERSION_NUMBER = "VERSION";
        /**
         * 单据号
         */
        protected static final String ORDER_NUMBER = "ORDER";

        private final Long objectVersionNumber;
        private final String orderNumber;
        private final OrderType orderType;

        public RedisOrderNumber(Long objectVersionNumber, String orderNumber, OrderType orderType) {
            this.objectVersionNumber = objectVersionNumber;
            this.orderNumber = orderNumber;
            this.orderType = orderType;
        }

        public Long getObjectVersionNumber() {
            return objectVersionNumber;
        }

        public String getOrderNumber() {
            return orderNumber;
        }

        public OrderType getOrderType() {
            return orderType;
        }
    }
}
